package reports;

import gui.common.ReportCallback;

import java.io.File;
import java.util.Date;

import model.entity.INode;
import model.entity.Item;
import model.entity.Product;
import model.management.ItemManager;
import model.management.ProductManager;

import org.junit.*;

import persistence.PersistenceMode;
import reports.visitor.IVisitor;
import common.Barcode;

/**
 * A common superclass to the Product Statistics report test suite. Implements methods for
 * generating a report without race conditions and also has a mock node abstract class for
 * overriding the visitor traversal methods in the ItemManager and ProductManager.
 * 
 * @author Ryan Cheatham
 */
public class ProductStatisticsReportTestSuperclass
{
  private Product product;

  // ////////////////////////////////////////////////////////////////////////////////////////////
  // Global presets
  // ////////////////////////////////////////////////////////////////////////////////////////////

  @BeforeClass
  public static void initialize()
  {
    // Delete serialized data
    File directory = new File(common.Persistence.filesPath);
    if (directory.exists())
    {
      File[] files = directory.listFiles();
      if (null != files)
      {
        for (int i = 0; i < files.length; i++)
        {
          files[i].delete();
        }
      }
    }
    directory.delete();

    PersistenceMode.setSystemMode(PersistenceMode.JavaSerialize);
  }

  @Before
  public void loadValues()
  {
    product = generateProduct();
  }

  @After
  public void resetManagers()
  {
    ItemManager.getInstance().setVisitorAcceptor(null);
    ProductManager.getInstance().setVisitorAcceptor(null);
  }

  @AfterClass
  public static void destroy()
  {

  }

  @Test
  public void emptyTest()
  {
    // this is just to make ant happy
  }

  // ////////////////////////////////////////////////////////////////////////////////////////////
  // Global methods
  // ////////////////////////////////////////////////////////////////////////////////////////////

  /**
   * Generates a new generic product with the default values.
   */
  protected Product generateProduct()
  {
    return new Product(new Barcode().getCode());
  }

  /**
   * Gets the default product for this test case.
   */
  protected Product getDefaultProduct()
  {
    return product;
  }

  /**
   * Sets the default product for this test case to the given product.
   * 
   * @param product
   *          The new default product for this test case.
   */
  protected void setDefaultProduct(Product product)
  {
    this.product = product;
  }

  /**
   * <p>
   * Generates a ProductStatisticsReport. Because the report is generated on another thread, this
   * method suspends until the report is completely generated to avoid race conditions.
   * </p>
   * <p>
   * <b><em>IMPORTANT NOTE:</em></b> Do not generate a report in any other way! Race conditions
   * WILL occur! If you are not familiar with the term 'race condition', be advised that it is by
   * far the most difficult error to track and fix.
   * </p>
   * 
   * @param months
   *          The number of months in the report.
   * @return The ProductStatisticsReport object after the report has fully generated.
   */
  protected ProductStatisticsReport generateReport(int months)
  {
    final Object lock = new Object();

    final ProductStatisticsReport report = new ProductStatisticsReport(months);
    report.generateReport(new ReportCallback(null)
    {
      @Override
      public void success()
      {}

      @Override
      public void finished()
      {
        synchronized (lock)
        {
          // the report has been generated, so release the lock to continue test execution
          lock.notify();
        }

        if (!report.success())
          org.junit.Assert.fail("The report was not generated successfully.");
      }

      @Override
      public void failure(Exception e)
      {
        e.printStackTrace();

        String message = "The report was not generated successfully. Exception: " + e;
        if (e.getMessage() != null)
          message += "\n\tException message: " + e.getMessage();

        org.junit.Assert.fail(message);
      }
    });

    synchronized (lock)
    {
      try
      {
        // wait for the report to be generated
        lock.wait();
      }
      catch (InterruptedException e)
      {
        org.junit.Assert.fail("The report generation thread was interrupted.");
      }
    }

    return report;
  }

  /**
   * <p>
   * Generates a ProductStatisticsReport. Because the report is generated on another thread, this
   * method suspends until the report is completely generated to avoid race conditions.
   * </p>
   * <p>
   * <b><em>IMPORTANT NOTE:</em></b> Do not generate a report in any other way! Race conditions
   * WILL occur! If you are not familiar with the term 'race condition', be advised that it is by
   * far the most difficult error to track and fix.
   * </p>
   * 
   * @param months
   *          The number of months in the report.
   * @param callback
   *          A custom callback to pass to the report generation method.
   * @return The ProductStatisticsReport object after the report has fully generated.
   */
  protected ProductStatisticsReport generateReport(int months, final ReportCallback callback)
  {
    final Object lock = new Object();

    final ProductStatisticsReport report = new ProductStatisticsReport(months);
    report.generateReport(new ReportCallback(null)
    {
      @Override
      public void success()
      {
        callback.success();
      }

      @Override
      public void finished()
      {
        synchronized (lock)
        {
          // the report has been generated, so release the lock to continue test execution
          lock.notify();
        }

        callback.finished();
      }

      @Override
      public void failure(Exception e)
      {
        callback.failure(e);
      }
    });

    synchronized (lock)
    {
      try
      {
        // wait for the report to be generated
        lock.wait();
      }
      catch (InterruptedException e)
      {
        org.junit.Assert.fail("The report generation thread was interrupted.");
      }
    }

    return report;
  }

  /**
   * Sets the visitor node for the Item Manager. This will be visited after the Product Manager
   * visitor node is visited during the report generation process.
   * 
   * @param node
   *          The visitor node to be used instead of the default Item Manager visitor node.
   */
  protected void setItemManagerVisitorNode(INode node)
  {
    ItemManager.getInstance().setVisitorAcceptor(node);
  }

  /**
   * Sets the visitor node for the Product Manager. This will be visited before the Item Manager
   * visitor node is visited during the report generation process.
   * 
   * @param node
   *          The visiotr node to be used instead of the default Product Manager visitor node.
   */
  protected void setProductManagerVisitorNode(INode node)
  {
    ProductManager.getInstance().setVisitorAcceptor(node);
  }

  // ////////////////////////////////////////////////////////////////////////////////////////////
  // Global helper classes
  // ////////////////////////////////////////////////////////////////////////////////////////////

  /**
   * An abstract class that allows mock visiting patterns among the managers. It also has
   * convenience methods for generating items quickly and simply.
   * 
   * @author Ryan Cheatham
   */
  protected abstract class MockNode implements INode
  {
    /**
     * Generates an item using the default product for this test case.
     * 
     * @param entryDate
     *          The entry date of this item. Cannot be <tt>null</tt> if the test is to work
     *          correctly.
     * @return a new item.
     */
    protected Item generateItem(Date entryDate)
    {
      return generateItem(getDefaultProduct(), entryDate, null);
    }

    /**
     * Generates an item using the default product for this test case.
     * 
     * @param entryDate
     *          The entry date of this item. Cannot be <tt>null</tt> if the test is to work
     *          correctly.
     * @param exitTime
     *          The exit time of this item. May be <tt>null</tt>.
     * @return a new item.
     */
    protected Item generateItem(Date entryDate, Date exitTime)
    {
      return generateItem(getDefaultProduct(), entryDate, exitTime);
    }

    /**
     * Generates an item using the given product.
     * 
     * @param product
     *          The product of the item to be generated.
     * @param entryDate
     *          The entry date of this item. Cannot be <tt>null</tt> if the test is to work
     *          correctly.
     * @param exitTime
     *          The exit time of this item. May be <tt>null</tt>.
     * @return a new item.
     */
    protected Item generateItem(Product product, Date entryDate, Date exitTime)
    {
      Item item = new Item(product, new Barcode(), entryDate, null, null);
      item.setExitTime(exitTime);
      return item;
    }

    @Override
    public void postOrderAccept(IVisitor visitor)
    {}
  }
}
